<?php


namespace BeReborn\Service\Http;

use BeReborn;
use BeReborn\Core\ArrayAccess;
use BeReborn\Service\Process\ServerInotify;
use BeReborn\Service\Process\ServerLogger;
use BeReborn\Service\Common\ServerStart;
use BeReborn\Service\Base\ServerBase;
use BeReborn\Service\Common\ServerPacket;
use BeReborn\Service\Common\ServerReceive;
use BeReborn\Service\Common\ServerRequest;
use BeReborn\Service\Common\ServerTask;
use BeReborn\Service\Common\ServerWebSocket;
use BeReborn\Service\Common\ServerWorker;
use BeReborn\Service\Common\ServerManager;
use BeReborn\Service\ServerConfig;
use Exception;
use Swoole\Coroutine;
use Swoole\Coroutine\System;
use Swoole\Process;
use Swoole\Runtime;

/**
 * Class Server
 * @package BeReborn\Service\Http
 */
class Server extends ServerBase
{
	public $service = '';

	public $host = '0.0.0.0';

	public $port = 50001;

	public $mode = SWOOLE_TCP;

	public $settings = [];

	public $enableWebSocket = true;

	public $socketPath = APP_PATH . '/app/Sockets';
	public $socketNamespace = 'App\Sockets';

	private $_websocket = ['0.0.0.0', 50002];

	private $_process = [
		'logger'  => ServerLogger::class,
		'inotify' => ServerInotify::class,
	];

	public $listens = [
		['host' => '0.0.0.0', 'port' => 40000, 'mode' => SWOOLE_TCP],
	];

	/**
	 * @param array $config
	 * @throws Exception
	 */
	public function setWebsocket(array $config)
	{
		if (!isset($config[0]) || !isset($config[1])) {
			throw new Exception('Websocket config must.');
		}
		$this->_websocket = $config;
	}

	/**
	 * @return array
	 */
	public function getWebsocket()
	{
		return $this->_websocket ?? ['0.0.0.0', 9500];
	}

	/**
	 * @param array $config
	 * @throws Exception
	 */
	public function setProcess(array $config)
	{
		if (empty($config)) {
			return;
		}
		$this->_process = array_merge($this->_process, $config);
	}

	/**
	 * @throws Exception
	 */
	public function onSnoozeInit()
	{
		$this->onSnoozeInnotifyCheck();

		if ($this->enableWebSocket !== true) {
			$this->server = new \Swoole\Http\Server($this->host, $this->port, SWOOLE_PROCESS, SWOOLE_SOCK_TCP);
		} else {
			$this->server = new \Swoole\WebSocket\Server($this->_websocket[0], $this->_websocket[1], SWOOLE_PROCESS, SWOOLE_SOCK_TCP);
			$this->server->addlistener($this->host, $this->port, SWOOLE_TCP);
		}
		$this->server->set($this->onSnoozeServerSettings());

		$this->onSnoozeWorkerListen();
		$this->onSnoozePipeMessageListen();
		$this->onSnoozeStartListen();
		$this->onSnoozeBatchProcess();
		$this->onSnoozeRequestListen();
	}


	/**
	 * @throws Exception
	 */
	public function onSnoozeServerSettings()
	{
		return $this->settings;
	}


	/**
	 * 热重载
	 */
	public function reload()
	{
		if (!$this->server) {
			return;
		}
		$this->server->reload();
	}

	/**
	 *
	 * @throws Exception
	 */
	private function onSnoozeInnotifyCheck()
	{
		BeReborn::clearAll();
		if (BeReborn::getPlatform()->isMac()) {
			return;
		}
		exec('cat /proc/sys/fs/inotify/max_user_watches', $result);
		if ((int)current($result) < 8192000) {
			shell_exec('echo 8192000 >> /proc/sys/fs/inotify/max_user_watches');
		}
	}


	/**
	 * @param $key
	 * @param $value
	 */
	public function patchConfig($key, $value)
	{
		$this->settings[$key] = $value;
	}

	/**
	 * @return bool
	 */
	public function isRunner()
	{
		if (empty($this->port)) {
			return false;
		}
		if (BeReborn::getPlatform()->isLinux()) {
			exec('netstat -tunlp | grep ' . $this->port, $output);
		} else {
			exec('lsof -i :' . $this->port . ' | grep -i "LISTEN"', $output);
		}
		if (!empty($output)) {
			return true;
		}
		return false;
	}

	/**
	 * @return int
	 */
	public function getRandWorker()
	{
		return rand(0, $this->settings['task_worker_num'] - 1);
	}

	/**
	 * @throws Exception
	 */
	public function onSnoozeBatchProcess()
	{
		if (!is_array($this->_process) || empty($this->_process)) {
			return;
		}
		foreach ($this->_process as $name => $process) {
			$this->setServerProcess($name, $process);
		}
	}

	/**
	 * @param $name
	 * @param $className
	 * @throws Exception
	 */
	private function setServerProcess($name, $className)
	{
		if (!class_exists($className)) {
			return;
		}
		$process = new $className(2, 1);
		if (BeReborn::getPlatform()->isLinux()) {
			$process->name($name);
		}
		app()->set($name, $process);
		$this->server->addProcess($process);
	}

	/**
	 * @return \Swoole\WebSocket\Server
	 * @throws Exception
	 */
	public function coreServerInit()
	{
		$this->onSnoozeInit();
		if ($this->enableWebSocket) {
			$this->onSnoozeManagerListen();
			$this->onSnoozeWebSocketListen();
		}
		if ($this->server->setting['task_worker_num'] > 0) {
			$this->onSnoozeTaskListen();
		}
		if (!empty($this->listens) && is_array($this->listens)) {
			$this->onSnoozeBatchListen();
		}

		$coroutineConfig = [
			'enable_deadlock_check' => false,
			'exit_condition'        => function () {
				return Coroutine::stats()['coroutine_num'] === 0;
			}
		];
		Coroutine::set($coroutineConfig);

		return $this->server;
	}


	/**
	 * @throws Exception
	 */
	public function onSnoozeBatchListen()
	{
		$this->debug('Swoole Http server listen ' . $this->host . ':' . $this->port);
		if ($this->enableWebSocket) {
			[$host, $port] = $this->_websocket;
			$this->debug('Swoole Socket server listen ' . $host . ':' . $port);
		}
		foreach ($this->listens as $listen) {
			if (!isset($listen['host'], $listen['port'], $listen['mode'])) {
				continue;
			}
			$this->debug('Swoole add listen ' . $listen['host'] . ':' . $listen['port']);
			$client = $this->server->addlistener($listen['host'], $listen['port'], $listen['mode']);
			$client->set($listen['settings'] ?? []);
			if ($listen['mode'] === SWOOLE_UDP || $listen['mode'] === SWOOLE_UDP6) {
				$this->onSnoozeUdpListen($client);
			} else {
				$this->onSnoozePacketListen($client);
			}
		}
	}


	/**
	 * @throws Exception
	 */
	public function onSnoozeManagerListen()
	{
		$manager = BeReborn::createObject(ServerManager::class);
		$this->server->on('managerStart', [$manager, 'onManagerStart']);
		$this->server->on('managerStop', [$manager, 'onManagerStop']);
	}


	/**
	 * @throws Exception
	 */
	public function onSnoozeWorkerListen()
	{
		/** @var ServerWorker $manager */
		$manager = BeReborn::createObject(ServerWorker::class);
		$manager->socketControllers = $this->socketPath;
		$this->server->on('workerStop', [$manager, 'onWorkerStop']);
		$this->server->on('workerExit', [$manager, 'onWorkerExit']);
		$this->server->on('workerStart', [$manager, 'onWorkerStart']);
		$this->server->on('workerError', [$manager, 'onWorkerError']);
	}


	/**
	 * @throws Exception
	 */
	public function onSnoozeRequestListen()
	{
		$manager = BeReborn::createObject(ServerRequest::class);
		$this->server->on('request', [$manager, 'onRequest']);
	}


	/**
	 * @throws Exception
	 */
	public function onSnoozePipeMessageListen()
	{
		$manager = BeReborn::createObject(ServerRequest::class);
		$this->server->on('pipeMessage', [$manager, 'onPipeMessage']);
	}

	/**
	 * @throws Exception
	 */
	public function onSnoozeStartListen()
	{
		$manager = BeReborn::createObject(ServerStart::class);
		$this->server->on('start', [$manager, 'onStart']);
	}


	/**
	 * @throws Exception
	 */
	public function onSnoozeTaskListen()
	{
		if (!isset($this->server->setting['task_enable_coroutine'])) {
			$this->server->setting['task_enable_coroutine'] = true;
		}
		$manager = BeReborn::createObject(ServerTask::class);
		if ($this->server->setting['task_enable_coroutine']) {
			$this->server->setting['task_object'] = true;
			$this->server->on('task', [$manager, 'onContinueTask']);
		} else {
			$this->server->on('task', [$manager, 'onTask']);
		}
		$this->server->on('finish', [$manager, 'onFinish']);
	}


	/**
	 * @param null $server
	 * @param array $config
	 * @throws Exception
	 */
	public function onSnoozeUdpListen($server = null, $config = [])
	{
		$manager = BeReborn::createObject(ServerPacket::class);
		$server->on('packet', [$manager, 'onPacket']);
	}

	/**
	 * @param null $server
	 * @param array $config
	 * @throws Exception
	 */
	public function onSnoozePacketListen($server = null, $config = [])
	{
		$manager = BeReborn::createObject(ServerReceive::class);
		$server->on('receive', [$manager, 'onReceive']);
	}


	/**
	 * @throws Exception
	 */
	public function onSnoozeWebSocketListen()
	{
		/** @var ServerWebSocket $manager */
		$manager = BeReborn::createObject(ServerWebSocket::class);
		$this->server->on('handshake', [$manager, 'onHandshake']);
		$this->server->on('message', [$manager, 'onMessage']);
		$this->server->on('close', [$manager, 'onClose']);
	}


	/**
	 * @return mixed
	 */
	public function rePutFile()
	{
		$file = $this->settings['log_file'];

		$runtime = BeReborn::$app->runtimePath . '/request.log/';
		@copy($file, $runtime . 'file_' . date('Y_m_d_H_i_s') . '.log');
		if (count(glob($runtime . '*')) >= 15) {
			shell_exec('find ' . $runtime . ' -mtime +15 -name "*.log" -exec rm -rf {} \;');
		}

		if (BeReborn::inCoroutine()) {
			System::writeFile($file, '');
		} else {
			file_put_contents($file, '');
		}
		return true;
	}
}
